// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 *******************************************************************************
 * Copyright (C) 2001-2013, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
package com.ibm.icu.impl;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * A Reader/Writer lock originally written for ICU service implementation. The internal
 * implementation was replaced with the JDK's stock read write lock (ReentrantReadWriteLock) for ICU
 * 52.
 *
 * <p>This assumes that there will be little writing contention. It also doesn't allow active
 * readers to acquire and release a write lock, or deal with priority inversion issues.
 *
 * <p>Access to the lock should be enclosed in a try/finally block in order to ensure that the lock
 * is always released in case of exceptions:<br>
 *
 * <pre>
 * try {
 *     lock.acquireRead();
 *     // use service protected by the lock
 * }
 * finally {
 *     lock.releaseRead();
 * }
 * </pre>
 *
 * <p>The lock provides utility methods getStats and clearStats to return statistics on the use of
 * the lock.
 */
public class ICURWLock {
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    private Stats stats = null;

    /** Internal class used to gather statistics on the RWLock. */
    public static final class Stats {
        /** Number of times read access granted (read count). */
        public int _rc;

        /** Number of times concurrent read access granted (multiple read count). */
        public int _mrc;

        /** Number of times blocked for read (waiting reader count). */
        public int _wrc; // wait for read

        /** Number of times write access granted (writer count). */
        public int _wc;

        /** Number of times blocked for write (waiting writer count). */
        public int _wwc;

        private Stats() {}

        private Stats(int rc, int mrc, int wrc, int wc, int wwc) {
            this._rc = rc;
            this._mrc = mrc;
            this._wrc = wrc;
            this._wc = wc;
            this._wwc = wwc;
        }

        private Stats(Stats rhs) {
            this(rhs._rc, rhs._mrc, rhs._wrc, rhs._wc, rhs._wwc);
        }

        /** Return a string listing all the stats. */
        @Override
        public String toString() {
            return " rc: " + _rc + " mrc: " + _mrc + " wrc: " + _wrc + " wc: " + _wc + " wwc: "
                    + _wwc;
        }
    }

    /** Reset the stats. Returns existing stats, if any. */
    public synchronized Stats resetStats() {
        Stats result = stats;
        stats = new Stats();
        return result;
    }

    /** Clear the stats (stop collecting stats). Returns existing stats, if any. */
    public synchronized Stats clearStats() {
        Stats result = stats;
        stats = null;
        return result;
    }

    /** Return a snapshot of the current stats. This does not reset the stats. */
    public synchronized Stats getStats() {
        return stats == null ? null : new Stats(stats);
    }

    /**
     * Acquire a read lock, blocking until a read lock is available. Multiple readers can
     * concurrently hold the read lock.
     *
     * <p>If there's a writer, or a waiting writer, increment the waiting reader count and block on
     * this. Otherwise increment the active reader count and return. Caller must call releaseRead
     * when done (for example, in a finally block).
     */
    public void acquireRead() {
        if (stats != null) { // stats is null by default
            synchronized (this) {
                stats._rc++;
                if (rwl.getReadLockCount() > 0) {
                    stats._mrc++;
                }
                if (rwl.isWriteLocked()) {
                    stats._wrc++;
                }
            }
        }
        rwl.readLock().lock();
    }

    /**
     * Release a read lock and return. An error will be thrown if a read lock is not currently held.
     *
     * <p>If this is the last active reader, notify the oldest waiting writer. Call when finished
     * with work controlled by acquireRead.
     */
    public void releaseRead() {
        rwl.readLock().unlock();
    }

    /**
     * Acquire the write lock, blocking until the write lock is available. Only one writer can
     * acquire the write lock, and when held, no readers can acquire the read lock.
     *
     * <p>If there are no readers and no waiting writers, mark as having an active writer and
     * return. Otherwise, add a lock to the end of the waiting writer list, and block on it. Caller
     * must call releaseWrite when done (for example, in a finally block).
     *
     * <p>
     */
    public void acquireWrite() {
        if (stats != null) { // stats is null by default
            synchronized (this) {
                stats._wc++;
                if (rwl.getReadLockCount() > 0 || rwl.isWriteLocked()) {
                    stats._wwc++;
                }
            }
        }
        rwl.writeLock().lock();
    }

    /**
     * Release the write lock and return. An error will be thrown if the write lock is not currently
     * held.
     *
     * <p>If there are waiting readers, make them all active and notify all of them. Otherwise,
     * notify the oldest waiting writer, if any. Call when finished with work controlled by
     * acquireWrite.
     */
    public void releaseWrite() {
        rwl.writeLock().unlock();
    }
}
